/**
 * Fichier Initialisation.java : Définition de classe
 * fr.unice.gnpt.core.init.Initialisation : Initialisateur
 * 
 * Auteurs : Groupe GNPT : Gastinel, Nicolas, Pellegrino, Trovato
 */
package fr.unice.gnpt.core.init;

import java.lang.reflect.InvocationTargetException;
import java.util.Collection;

import javax.swing.JOptionPane;
import javax.swing.SwingUtilities;

import fr.unice.gnpt.Main;
import fr.unice.gnpt.core.Cours;
import fr.unice.gnpt.core.dao.DaoCours;
import fr.unice.gnpt.core.dao.DaoException;
import fr.unice.gnpt.core.dao.connexion.ConnexionException;
import fr.unice.gnpt.core.dao.factory.FabriqueDao;
import fr.unice.gnpt.core.dao.factory.TypeFabriqueDao;
import fr.unice.gnpt.core.util.Journal;
import fr.unice.gnpt.core.util.Parametres;
import fr.unice.gnpt.core.util.ParametresException;
import fr.unice.gnpt.gui.Identification;

/**
 * Classe d'initialisation des données.
 * <p>
 * Cette classe s'occupe de préparer les données, en demandant préalablement à
 * l'utilisateur comment il souhaite les enregistrer (Gestionnaire de
 * persistance), et s'occupe de demander à l'utilisateur les informations
 * nécessaires à utiliser ledit gestionnaire. Ensuite l'initialisateur va se
 * connecter à la base de données choisie et la remplir de données initiales si
 * elle est vide.
 * <p>
 * <b>Attention :</b><br>
 * Cet objet <b>ne doit pas</b> être utilisé dans l'Event Dispatch Thread, en
 * raison des accès disques et base de données qui peuvent être longs et donc
 * <i>geler</i> l'application Swing qui l'appellerait directement.
 * 
 * @author gnpt
 * @version O.2.1
 */
public final class Initialisation {

	/** Est-ce que l'initialisation a été faite ? */
	private static boolean INITIALISATION_FAITE = false;

	/** Objet initialisation */
	private static Initialisation instance;

	/** Journal */
	private Journal journal;

	/**
	 * Constructeur vide (Singleton Pattern).
	 */
	private Initialisation() {
		// On récupère le journal du Main qui l'a déjà initialisé
		this.journal = Main.getJournal();
	}

	/**
	 * Constructeur indirect pour un objet Initialisation (singleton pattern).
	 * <p>
	 * Une fois l'initialisation effectuée, cet méthode renverra null.
	 * L'instance interne ne sera pas forcément détruite.
	 * 
	 * @return Une instance d'initialisation.
	 */
	public static Initialisation newInstance() {
		if (Initialisation.INITIALISATION_FAITE)
			return null;
		if (instance == null)
			instance = new Initialisation();
		return instance;
	}

	/**
	 * Démarrage de l'initialisation.
	 * 
	 * @throws ParametresException
	 */
	public void startInitialisation() {
		// Est-ce que l'initialisation a été faite ?
		if (Initialisation.INITIALISATION_FAITE) { throw new IllegalStateException(
				"Initialisation déjà effectuée."); }

		this.journal
				.logInfo("Lancement de la fenêtre de demande d'informations.");

		final Identification ident = new Identification();

		if (Main.isModeGraphique()) {
			try {
				/**
				 * L'objet self permet de référencer l'instance courante dans
				 * lors de la création de l'objet Runnable, car sinon this se
				 * réfère au Runnable et non à Initialisation.
				 */
				final Initialisation self = this;

				SwingUtilities.invokeAndWait(new Runnable() {
					public void run() {
						ident.setThreadAttente(self);
						ident.creerEtAfficher();
					}
				});
				this.journal
						.logInfo("Attente de la fenêtre de demande des préférences");
				synchronized (this) {
					this.wait();
				}
				Main.setTypeUtilisateur(ident.getTypeUtilisateur());
			} catch (InterruptedException e) {
				this.journal.logException(e,
						"Echec de la demande de préférences", true, true);
			} catch (InvocationTargetException e) {
				this.journal.logException(e,
						"Echec de la demande de préférences", true, true);
			}
		}

		Parametres param = Parametres.getParametres();
		String gestionnaire = param.getGestionnairePersistance();

		/*
		 * lecture des préférences, configuration de la fabrique DAO, ouverture
		 * de la base, et recherche de données
		 */
		if (gestionnaire.equalsIgnoreCase("jpa")) {
			FabriqueDao.setTypeFabriqueDao(TypeFabriqueDao.JPA);
			FabriqueDao.setNomFichierConfiguration(null);

		} else if (gestionnaire.equalsIgnoreCase("db4o")) {
			FabriqueDao.setTypeFabriqueDao(TypeFabriqueDao.DB4O);
			FabriqueDao.setNomFichierConfiguration(param.getCheminDb4o());

		} else {
			Main.getJournal().logErreur(
					"Gestionnaire de persistance non défini.");
			Main.exit(1);
		}
		FabriqueDao fab = null;
		DaoCours daoCours = null;
		Collection<Cours> coll = null;
		boolean doitInitialiserDonnees = false;
		this.journal.logInfo("Récupération des données de la base.");
		try {
			fab = FabriqueDao.getFabriqueDao();
			daoCours = fab.getDaoCours();
			daoCours.getConnexion().ouvrir();
			coll = daoCours.findAll();
			/*
			 * On récupère la taille de la collection maintenant, sinon on a une
			 * exception de type com.db4o.ext.DatabaseClosedException. L'objet
			 * coll n'est plus utilisable une fois la connexion fermée.
			 */
			doitInitialiserDonnees = (coll.size() == 0);
			coll = null;
		} catch (ConnexionException e) {
			// Exec de la connexion, c'est récupérable.
			this.journal.logException(e, "Echec de connexion", true, true);
			JOptionPane.showMessageDialog(null,
					"Un problème est survenue lors de la connexion à la base de données :\n"
							+ e.getLocalizedMessage(),
					"Problème de connexion.", JOptionPane.ERROR_MESSAGE);
			// On ferme la fenêtre d'identification
			ident.dispose();
			// On redemande les infos de connexion
			this.startInitialisation();
			return;
		} catch (DaoException e) {
			// Ici ce n'est pas récupérable, donc euh on meurt ?
			this.journal.logException(e, "Echec d'accès aux données", true,
					true);
			Main.exit(1);
		} finally {
			try {
				if (daoCours != null)
					daoCours.getConnexion().fermer();
			} catch (ConnexionException e) {
				this.journal.logException(e, "Echec de la fermeture", false, true);
			}
		}

		if (doitInitialiserDonnees) {
			try {
				InitialisationBase iniBase = new InitialisationBase();
				this.journal.logInfo("Remplissage de la base de données.");
				iniBase.remplirBase();
			} catch (DaoException e) {
				this.journal.logException(e, "Initialisation des données",
						true, true);
				JOptionPane.showMessageDialog(null,
						"Un problème est survenue lors de l'accès aux données",
						"Problème de connexion.", JOptionPane.ERROR_MESSAGE);
			}
		}
		
		PostInitialisation postInit = new PostInitialisation();
		postInit.doStuff();
		postInit = null;

		Initialisation.INITIALISATION_FAITE = true;
		Initialisation.instance = null;
		
		if(ident.isVisible()) {
			ident.dispose();
		}

		this.journal.logInfo("Initialisation terminée.");
	}
}
